package sa.huang;


import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.Iterator;

import javax.xml.bind.JAXBContext;
import javax.xml.bind.JAXBElement;
import javax.xml.bind.JAXBException;
import javax.xml.bind.Marshaller;
import javax.xml.bind.Unmarshaller;
import javax.xml.namespace.QName;

import org.oasis_open.docs.wsbpel._2_0.process._abstract.*;
/**
 * @author huang
 * class PBDHandler encapsulates the methods that modify the PBD into PBEL
 * 
 */
public class PBDHandler {
	
	private JAXBContext jaxbContext;
	
	private TProcess process = null;

	public PBDHandler() {
		try {
			jaxbContext = JAXBContext.newInstance("org.oasis_open.docs.wsbpel._2_0.process._abstract");
		} catch (JAXBException e) {
			e.getStackTrace();
		}
	}
	public TProcess loadProcess(String filePath) {
		try {
			Unmarshaller u = jaxbContext.createUnmarshaller();
			process = (TProcess)((JAXBElement<?>) u.unmarshal( new FileInputStream(filePath))).getValue();			
		} catch (IOException e) {
			e.printStackTrace();
		} catch (JAXBException e) {
			e.printStackTrace();
		}
		
		return process;
	}
	
	/**
	 * based on algorithm 3.6.
	 * The method is used to generate corresponding partnerLinks in scope or process.
	 * The method will be called during the method modifyConstruct(Object o, Data data) is being called.
	 * 
	 * @param o could be a process:TProcess or a scope:TScope 
	 * @param data partnerLinks_scopeMap in data:Data is used to decide which partnerLink should be declared in which process or scope
	 * 
	 */
	private void declarePartnerLinks(Object o, Data data){
		if(o instanceof TProcess){
			Iterator<QName> iter = data.getPartnerLinks_scopeMap().keySet().iterator();		// traverse partnerLinks_scopeMap
			while(iter.hasNext()){					
				QName key = iter.next();
				if(key.getLocalPart().equals(((TProcess) o).getName())){					// if the local part of the QName that is stored in partnerLinks_scopeMap equals the name of the process 
							System.out.println("      the process:"+key+" is in scopeSet");
					HashSet<String> sc = data.getPartnerLinks_scopeMap().get(key);			// get the HashSet of partnerLinks that are indexed by the QName key
					TPartnerLinks tpls = new TPartnerLinks();								
					for(Iterator<String> iter_1 = sc.iterator(); iter_1.hasNext();){		// get all the partnerLinks' names
						String pl = iter_1.next();
								System.out.println("      pls should be declared in this process: "+pl);
						
					
						tpls.getPartnerLink().add(generatePartnerLink(pl, data));			// put each object partnerLink into object partnerLinks
						
					}
					((TProcess) o).setPartnerLinks(tpls);									// put object partnerLinks into object process
				}
				
			}
		}
		if(o instanceof TScope){															// basically the same as above, but the identifier of scope is wsu:id
			Iterator<QName> iter = ((TScope) o).getOtherAttributes().keySet().iterator();	// get the wsu:id of the scope
			while(iter.hasNext()){
				QName key = iter.next();
				String value = ((TScope) o).getOtherAttributes().get(key);					
						System.out.println("      the scope with wsu:id : "+value+" is in scopeSet");
				
				for(Iterator<QName> iter_1 = data.getScopeSet().iterator(); iter_1.hasNext();){	// traverse the scopeSet
					QName sc = iter_1.next();													
					if(value.equals(sc.getLocalPart())){										// for the wsu:id in the scopeSet that equals wsu:id of the scope
						HashSet<String> pls = data.getPartnerLinks_scopeMap().get(sc);			
						TPartnerLinks tpls = new TPartnerLinks();
						for(Iterator<String> iter_2 = pls.iterator(); iter_2.hasNext();){
							String pl = iter_2.next();
									System.out.println("      pls should be declared in this scope: "+pl);
							
							tpls.getPartnerLink().add(generatePartnerLink(pl, data));
						}
						((TScope) o).setPartnerLinks(tpls);
					}
				}
			}
		}
	}
	/**
	 * generate an object:TPartnerLink using the information of partnerLinkType and myRole/partnerRole that are related with input String pl
	 * @param pl
	 * @param data
	 * @return TPartnerLink that is generated through the plt and myRole/partnerRole that are related with input String pl
	 * 
	 */
	private TPartnerLink generatePartnerLink(String pl, Data data){
		TPartnerLink tpl = new TPartnerLink();
		String plt = data.plType_pl(pl);
		String mRole = data.myRole_pl(pl);
		String pRole = data.partnerRole_pl(pl);
		tpl.setName(pl);
		tpl.setPartnerLinkType(plt);
		tpl.setMyRole(mRole);
		tpl.setPartnerRole(pRole);
		return tpl;
	}
	
	/**
	 * based on algorithm 3.9 
	 * this method is used to modify activities in PBD into BPEL 
	 * because all the activities are declared in tag <process>, so the method will firstly take an object:TProcess as input 
	 * and for all the activities declared in the <sequence> in this <process>, this method will be recursively called.
	 * @param o
	 * @param data
	 */
	public void modifyConstruct(Object o, Data data){
		
		/**
		 * modify <process>
		 * tags normally declared under <process>:
		 * <import>
		 * <partnerLinks>
		 * <variables>
		 * <correlationSets>
		 * <sequence>
		 * 
		 * 
		 * TODO
		 * generate variable
		 * generate import
		 */
		if(o instanceof TProcess){
			
			declarePartnerLinks(o, data);							// generate <partnerLinks>
			
			if(((TProcess) o).getCorrelationSets() != null){		
				for(TCorrelationSet tcorr:((TProcess) o).getCorrelationSets().getCorrelationSet()){
					modifyConstruct(tcorr, data);					// modify <correlationSet>
				}
			}
			
			TSequence tseq = ((TProcess) o).getSequence();			
			modifyConstruct(tseq, data);							// modify <sequence>
		}
		
		/**
		 * modify <correlationSet>
		 */
		if(o instanceof TCorrelationSet){
			ArrayList<String> properties = new ArrayList<String>();	// JAXB transforms properties in BPEL into List<String>
			for(String prop:((TCorrelationSet) o).getProperties()){
				String prefix = data.nsprefix_Property(data.property_CorrPropName(prop));
				String property = prefix.concat(":").concat(prop);	
				properties.add(property);
			}
			((TCorrelationSet) o).getProperties().clear();			// properties in PBD don't have prefix, remove them 
			((TCorrelationSet) o).getProperties().addAll(properties);// add newly generated properties:List<String> 
		}
		
		/**
		 * modify <sequence>
		 * tags normally declared under <sequence>:
		 * <receive>, <invoke> and other basic activities
		 * <sequence>, <scope> and other structured activities
		 * i only implement part of the activities here, which are implemented in BPEL4Chor Designer
		 */
		if(o instanceof TSequence){
			for(Object tact:((TSequence) o).getActivity()){
				if(tact instanceof TInvoke){
					modifyMessageConstruct(tact, data);
				}
				if(tact instanceof TReceive){
					modifyMessageConstruct(tact, data);
				}
				if(tact instanceof TOpaqueActivity){
					TOpaqueActivity topa = (TOpaqueActivity)tact;
					topa.getOtherAttributes().clear();
				}
				if(tact instanceof TScope){
					modifyConstruct(tact, data);
				}
				if(tact instanceof TForEach){
					modifyConstruct(tact, data);
				}
				if(tact instanceof TPick){
					modifyConstruct(tact, data);
				}
				if(tact instanceof TAssign){
					modifyConstruct(tact, data);
				}
				/**
				 * TODO
				 * all other activities
				 */
			}
			((TSequence) o).getOtherAttributes().clear();
		}
		/**
		 * modify <scope>
		 * tags normally declared under <scope>:
		 * <partnerLinks>
		 * <sequence>
		 * 
		 */
		if(o instanceof TScope){
			declarePartnerLinks(o, data);
			TSequence tseq = ((TScope) o).getSequence();
			modifyConstruct(tseq, data);
			((TScope) o).getOtherAttributes().clear();				// delete wsu:id
		}
		/**
		 * modify <forEach>
		 * tags normally declared under <forEach>:
		 * <startCounterValue>
		 * <finalCounterValue>
		 * <completionCondition>
		 * <scope>
		 */
		if(o instanceof TForEach){
			TForEach tfe = (TForEach)o;
			String counterName = "i";								// attribute of <forEach>
			tfe.setCounterName(counterName);
			
			TExpression startCounter = new TExpression();			// setStartCounterValue() generated by JAXB need an object:TExpression as parameter
			startCounter.setExpressionLanguage("urn:oasis:names:tc:wsbpel:2.0:sublang:xpath2.0");
			String startCounterValue = "1";							// array in BPEL is indexed from 1
			startCounter.getContent().add(startCounterValue);
			TExpression finalCounter = new TExpression();
			finalCounter.setExpressionLanguage("urn:oasis:names:tc:wsbpel:2.0:sublang:xpath2.0");
			String finalCounterValue = "size of arrayOfEPRs[".concat(counterName).concat("]");	// size of array in BPEL could be calculated using method count()
			finalCounter.getContent().add(finalCounterValue);
			tfe.setStartCounterValue(startCounter);
			tfe.setFinalCounterValue(finalCounter);
			
			/**
			 * TODO
			 * might need to modify <completionCondition>
			 */
			
			TScope tsc = tfe.getScope();
						
			TAssign tassign = new TAssign();
			TCopy tcopy = new TCopy();
			TFrom tfrom = new TFrom();
			tfrom.setVariable("arrayOfEPRs[".concat(counterName).concat("]"));
			tcopy.setFrom(tfrom);
			TTo tto = new TTo();
			
			/**
			 *  set the proper partnerLink
			 *  similar with declarePartnerLinks() 
			 */
			Iterator<QName> iter = tsc.getOtherAttributes().keySet().iterator();
			while(iter.hasNext()){
				QName key = iter.next();
				String value = tsc.getOtherAttributes().get(key);
				
				for(Iterator<QName> iter_1 = data.getScopeSet().iterator(); iter_1.hasNext();){
					QName sc = iter_1.next();
					if(value.equals(sc.getLocalPart())){
						HashSet<String> pls = data.getPartnerLinks_scopeMap().get(sc);
						for(Iterator<String> iter_2 = pls.iterator(); iter_2.hasNext();){
							String pl = iter_2.next();
							tto.setPartnerLink(pl);
						}
					}
				}
			}
			
			tcopy.setTo(tto);
			tassign.getCopyOrExtensionAssignOperation().add(tcopy);
			
		
			/**
			 * add assign to activity list
			 * the tseq:TSequence contains activities that are declared in BPD files
			 * the activity assign that was generated above should be added before all other activities
			 */
			TSequence tseq = tsc.getSequence();
			
			for(int i = (tseq.getActivity().size() -1); i>=0; i--){
				tseq.getActivity().add(i+1,tseq.getActivity().get(i));
				tseq.getActivity().remove(i);
			}		
			tseq.getActivity().add(0, tassign);
			
			
			
			modifyConstruct(tsc, data);
			((TForEach) o).getOtherAttributes().clear();
		}
		
		/**
		 * tags under <pick>:
		 * <OnMessage>
		 * <OnAlarm>
		 *
		 */
		if(o instanceof TPick){
			for(TOnMessage tom: ((TPick) o).getOnMessage()){
				modifyMessageConstruct(tom, data);
				TSequence tseq = tom.getSequence();
				modifyConstruct(tseq, data);
				tom.getOtherAttributes().clear();
			}
			for(TOnAlarmPick toap: ((TPick) o).getOnAlarm()){
				TSequence tseq = toap.getSequence();
				modifyConstruct(tseq, data);
				toap.getOtherAttributes().clear();
			}
			((TPick) o).getOtherAttributes().clear();
		}
		/**
		 * TODO
		 * all other activities
		 */
				
		
	}

	private void modifyMessageConstruct(Object o, Data data){
		if(o instanceof TInvoke){
			String id = getWSUID(o);
			String pl = data.partnerLink_MCID(id);				// data.partnerLink_MCMap is initialised and updated in method traverseCom()
			String op = data.operation_MCID(id);				// data.operation_MCMap is initialised and updated in method analyzeMessageLinksInGrounding()
			QName qpt = data.portType_MC(id);					// data.portType_MCMap is initialised and updated in method analyzeMessageLinksInGrounding()
			String pt = qNameToNcName(qpt);
			
			((TInvoke) o).setPartnerLink(pl);
			((TInvoke) o).setOperation(op);
			((TInvoke) o).setPortType(pt);
					
					System.out.println(id);
					System.out.println(pl);
			((TInvoke) o).getOtherAttributes().clear();
		}
		if(o instanceof TReceive){
			String id = getWSUID(o);
			String pl = data.partnerLink_MCID(id);
			String op = data.operation_MCID(id);
			QName qpt = data.portType_MC(id);
			String pt = qNameToNcName(qpt);
			
			((TReceive) o).setPartnerLink(pl);
			((TReceive) o).setOperation(op);
			((TReceive) o).setPortType(pt);
			
			((TReceive) o).getOtherAttributes().clear();
		}
		if(o instanceof TOnMessage){
			String id = getWSUID(o);
			String pl = data.partnerLink_MCID(id);
			String op = data.operation_MCID(id);
			QName qpt = data.portType_MC(id);
			String pt = qNameToNcName(qpt);
			
			((TOnMessage) o).setPartnerLink(pl);
			((TOnMessage) o).setOperation(op);
			((TOnMessage) o).setPortType(pt);
			
			((TOnMessage) o).getOtherAttributes().clear();
		}
	}
	/**
	 * get the wsu:id attribute 
	 * assume that each activity only has one OtherAttribute, which is wsu:id
	 * @param o
	 * @return wsu:id
	 */
	public String getWSUID(Object o){
		String id = new String();
		if(o instanceof TInvoke){
			Iterator<QName> iter = ((TInvoke) o).getOtherAttributes().keySet().iterator();
			QName key = iter.next();
			String value = ((TInvoke) o).getOtherAttributes().get(key);
			id = value;			
		}
		if(o instanceof TReceive){
			Iterator<QName> iter = ((TReceive) o).getOtherAttributes().keySet().iterator();
			QName key = iter.next();
			String value = ((TReceive) o).getOtherAttributes().get(key);
			id = value;
		}
		if(o instanceof TOnMessage){
			
			Iterator<QName> iter = ((TOnMessage) o).getOtherAttributes().keySet().iterator();
				iter.next();										//the first OtherAttribute of <onMessage> is "name"
				QName key = iter.next();
				String value = ((TOnMessage) o).getOtherAttributes().get(key);
				id = value;
		}
		
		return id;
	}
	public String qNameToNcName(QName q){
		return q.getPrefix().concat(":").concat(q.getLocalPart());
	}
	
	public void writeProcess(TProcess newProcess, String filePath){
		
		try{
			Marshaller m = jaxbContext.createMarshaller();
			File newFile = new File(filePath);
			m.setProperty(Marshaller.JAXB_FORMATTED_OUTPUT, true);	
			m.marshal(new JAXBElement<TProcess>(new QName("http://docs.oasis-open.org/wsbpel/2.0/process/abstract","process"), TProcess.class, newProcess), newFile);
		} catch (JAXBException e) {
			e.printStackTrace();
		} 
		
		
	}
	

			
		
		

}
